/*******************************************************************************
 * Copyright 2011 @ Kapil Viren Ahuja
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.kapil.framework.exception;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import com.kapil.framework.lang.StringUtils;
import com.kapil.framework.reader.IMessageReader;
import com.kapil.framework.reader.MessageReaderFactory;


/**
 * <p>
 * Base class for exceptions that can be thrown by the application. It provides a base implementation of the following
 * best practices often desired in most exception handling frameworks:
 * </p>
 * <p>
 * <li>
 * <b>Unchecked exception classes: </b>Unchecked (or runtime) exceptions free up developers from having to worry about
 * catching all exceptions close to where the exception was thrown. This allows application code to be less cluttered,
 * while at the same time allowing developers to catch and act upon exceptions that must be caught and handled before
 * the application can proceed.</li>
 * <li>
 * <b>Message externalisation: </b>Exceptions are always associated with messages. At a bare minimum, exceptions will
 * have a message that may be written to an application log. Additionally, they may have a user message that must be
 * shown on a front-end to the user. A good exception handling framework for applications should externalize messages
 * associated with exceptions. Further, these messages should be modifiable, without the need to re-compile the code.
 * This allows flexibility in changing error messages for the application at will.</li>
 * <li>
 * <b>Message localisation: </b>If exceptions are expected to lead to error messages being shown to the application
 * users on the front-end, the exceptions framework must provide for a mechanism to make the messages localizable. This
 * allows messages (specially those shown to users) to be configured in user's own language.</li>
 * <li>
 * <b>Error logging: </b>Developers can optionally generate application log messages and write them to a persistent
 * store when an exception is caught.</li>
 * <li>
 * <b>Configirable Resources: </b> Your application if wants to use a new set of resource files, they can add it to the 
 * existing data sources by using the {@link BaseException#addDataSource(String)} method. This needs to configured once
 * for the code. You can refer to the implementation in the test cases.
 * </p>
 */
public abstract class BaseException extends RuntimeException
{
    private static final long     serialVersionUID  = -8885543809604083812L;

    /**
     * Holds the various data sources form which the Exception classes should read the messages.
     */
    private static List<String>   dataSources       = new ArrayList<String>(Arrays.asList("FrameworkCoreMessages"));

    private static IMessageReader coreMessageReader = MessageReaderFactory.getInstance().getReader("ResourceBundle", dataSources);

    private transient String      _ErrorCode;
    private transient String      _ErrorMessage;

    static
    {

    }


    /**
     * Initialises error code for the exception. The error code is used for creating a string message that can be
     * written to application logs when the exception is caught.
     * 
     * @param errorCode An Error Code.
     */
    protected BaseException(final String errorCode)
    {
        super(getErrorMessage(errorCode, null));
        init(errorCode, null);
    }


    /**
     * Initialises error code for the exception and creates a dynamic log message that includes contextual information
     * collected at the time of raising the exception.
     * 
     * @param errorCode An Error Code.
     * @param tokens An array of @see java.lang.String values containing token values to be put in the log message.
     */
    protected BaseException(final String errorCode, final String[] tokens)
    {
        super(getErrorMessage(errorCode, tokens));
        init(errorCode, tokens);
    }


    /**
     * Initialises the error code for the exception and adds another exception in the exception chain.
     * 
     * @param errorCode An Error Code.
     * @param linkedException A @see java.lang.Throwable that needs to be linked to the current exception.
     */
    protected BaseException(final String errorCode, final Throwable linkedException)
    {
        super(getErrorMessage(errorCode, null), linkedException);
        init(errorCode, null);
    }


    /**
     * Initialises error code for the exception, adds another exception in the exception chain and creates a dynamic log
     * message.
     * 
     * @param errorCode An Error Code.
     * @param tokens An array of @see java.lang.String values containing token values to be put in the log message.
     * @param linkedException A @see java.lang.Throwable that needs to be linked to the current exception.
     */
    protected BaseException(final String errorCode, final String[] tokens, final Throwable linkedException)
    {
        super(getErrorMessage(errorCode, tokens), linkedException);
        init(errorCode, tokens);
    }


    /**
     * Returns the error code for the exception.
     * 
     * @return A @see java.lang.String value containing the error code.
     */
    public String getErrorCode()
    {
        return _ErrorCode;
    }


    /**
     * Returns the user message for the exception. This message is typically shown to the application users on the
     * front-end when the exception is raised.
     * 
     * @return A @see java.lang.String value containing the user message.
     */
    public String getErrorMessage()
    {
        return _ErrorMessage;
    }


    /**
     * Gets error message from a resource bundle using the error code as the key.
     * 
     * @param code A string key to locate the base message.
     * @param tokens An array of strings containing values to be dynamically populated in the error message.
     * @return A @see java.lang.String containing a dynamically created error message.
     */
    private static String getErrorMessage(final String code, final String[] tokens)
    {
        return StringUtils.replaceTokens(coreMessageReader.getMessage(code, Locale.getDefault()), "##", tokens);
    }


    private void init(final String errorCode, final String[] tokens)
    {
        _ErrorCode = errorCode;
        _ErrorMessage = getErrorMessage(errorCode, tokens);

        if (_ErrorMessage == null)
        {
            _ErrorMessage = errorCode;
        }
    }


    /**
     * Adds an additional data-source to the existing list of data sources. Exception classe by default uses the
     * FrameworkTestMessages.properties file to read all messages into the {@link IMessageReader}. If you wish to add
     * more data sources for your framework/project. 
     * 
     * when creating a new Exception that extends {@link BaseException}, all the data sources are searched.
     * 
     * @param dataSource to be added to existing list of data sources.
     */
    public static void addDataSource(String dataSource)
    {
        dataSources.add(dataSource);
    }
}
